ciftiTools Demo

Damon Pham & Amanda Mejia

ciftiTools is an R package for working with CIFTI-2 format brain imaging data. Used in conjunction with GIFTI surface geometry files, CIFTI files enable surface-based analysis of grey matter data, which has several advantages over traditional volumetric/voxel-based analysis. Because of this, the CIFTI-2 format is used by recent neuroimaging studies including the Human Connectome Project (HCP). ciftiTools supports reading, writing, visualizing, resampling, and other operations for CIFTI files with the ".dscalar.nii", ".dtseries.nii", and ".dlabel.nii" intents. Several of these operations are made possible by the Connectome Workbench.

To get started, the first time you use the ciftiTools package, install it via either CRAN (with install.packages()) or Github (with install_github() from the devtools package). Here, we will use the CRAN version.

# Check if package installed. If not, install it.
if(!require('ciftiTools')){
  install.packages('ciftiTools')
  #install_github('mandymejia/ciftiTools') # development version
  library(devtools)
}
## Loading required package: ciftiTools

Now we load the ciftiTools package.

library(ciftiTools)

Next, we indicate where to find the Connectome Workbench. This can be the full path to the Connectome Workbench executable file, or the path to its containing folder, in which case ciftiTools will locate the full path. Here, we will use the latter:

# Replace '/Applications/workbench' with the actual path to 
#   the Connectome Workbench folder on your computer.
#   If successful, the full path to the Workbench executable will be printed.
ciftiTools.setOption('wb_path', '../../workbench')
## Using this Workbench path: '../../workbench/bin_windows64/wb_command.exe'.

In this vignette, we will use example data included in the ciftiTools package. The files are originally from NITRC:

We will also use GIFTI files containing very inflated surface geometry.

cifti_fnames <- ciftiTools::demo_files()$cifti
surfL_fname <- ciftiTools::demo_files()$surf["left"]
surfR_fname <- ciftiTools::demo_files()$surf["right"]

Reading and Writing

Reading

CIFTI files organize the gray matter of the brain into “greyordinates”: vertices representing the left and right cortical surfaces, and voxels representing the subcortical gray matter structures and the cerebellum. A CIFTI file consists of two parts: (1) a NIFTI XML header which contains all the metadata including medial wall locations, subcortical structure labels, and the subcortical volumetric mask; and (2) a matrix representing all the greyordinate data. These components are read in together with read_cifti:

basename(cifti_fnames["dscalar"])
## [1] "Conte69.MyelinAndCorrThickness.6k_fs_LR.dscalar.nii"
xii <- read_xifti(cifti_fnames["dscalar"])
xii
## Brain Structures: left cortex, right cortex  
##  left cortex: 5412 surface vertices, 2 measurements.
##  right cortex: 5434 surface vertices, 2 measurements.
## Intent: 3006 (dscalar).
#plot(xii) # for visualization, we'll get to that next!

By default, read_cifti only reads in the left and right cortex data. The subcortical data can be included by using the argument brainstructures="all". Other brainstructure combinations can be specified too, e.g. brainstructures=c("left", "subcortical"). The full set of choices for brainstructures is any combination of "left", "right" and "subcortical", or "all" for all three.

The resulting object produced by read_cifti is a "xifti" with components data (the greyordinate data matrix, separated by brainstructure), meta (metadata, most of which is from the NIFTI XML header), and surf (surface geometry). The last component distinguishes a "xifti" from a CIFTI: the left and right cortical surface geometries are not included in CIFTI files, so they must be read from separate surface GIFTI files (ending in surf.gii). The surface must be compatible: the number of vertices must be the same, and each vertex in the CIFTI data must correspond to the vertex location in the corresponding GIFTI surface file. In this way, a "xifti" represents a combination of a CIFTI file with compatible GIFTI files for the cortical mesh.

We can add GIFTI surface geometries like so:

basename(cifti_fnames["dtseries"])
## [1] "Conte69.MyelinAndCorrThickness.32k_fs_LR.dtseries.nii"
xii <- read_xifti(cifti_fnames["dtseries"])
xii <- add_surf(xii, surfL=surfL_fname, surfR=surfR_fname)
xii
## Brain Structures: left cortex, right cortex  
##  left cortex: 30424 surface vertices, 2 measurements.
##      left surface model is present.
##  right cortex: 30527 surface vertices, 2 measurements.
##      right surface model is present.
## Intent: 3002 (dtseries).

Alternatively, we could have provided the surface geometries at the outset of reading the CIFTI file:

xii2 <- read_xifti(cifti_fnames["dtseries"], surfL_fname=surfL_fname, surfR_fname=surfR_fname)
all.equal(xii, xii2) # same result
## [1] TRUE

To only read the CIFTI header, use info_cifti:

xii_info <- ciftiTools::info_cifti(cifti_fnames["dscalar"])
str(xii_info) # shows header structure
## List of 3
##  $ cortex :List of 2
##   ..$ medial_wall_mask:List of 2
##   .. ..$ left : logi [1:5762] TRUE TRUE TRUE TRUE TRUE TRUE ...
##   .. ..$ right: logi [1:5762] TRUE TRUE TRUE TRUE TRUE TRUE ...
##   ..$ resamp_res      : NULL
##  $ subcort:List of 3
##   ..$ labels   : NULL
##   ..$ mask     : NULL
##   ..$ trans_mat: NULL
##  $ cifti  :List of 4
##   ..$ intent         : num 3006
##   ..$ brainstructures: chr [1:2] "left" "right"
##   ..$ names          : chr [1:2] "MyelinMap_BC_decurv" "corrThickness"
##   ..$ misc           :List of 4
##   .. ..$ ParentProvenance : chr "C:\\Users\\damon\\AppData\\Local\\Temp\\Rtmp2X6THF/resampled_6000_Conte69.MyelinAndCorrThickness.32k_fs_LR.L.fu"| __truncated__
##   .. ..$ ProgramProvenance: chr "Connectome Workbench\nType: Command Line Application\nVersion: 1.4.2\nQt Compiled Version: 5.7.0\nQt Runtime Ve"| __truncated__
##   .. ..$ Provenance       : chr "C:\\Users\\damon\\Desktop\\WORKBE~1\\BIN_WI~1\\WB_COM~1.EXE -cifti-create-dense-scalar C:\\Users\\damon\\Deskto"| __truncated__
##   .. ..$ WorkingDirectory : chr "C:/Users/damon/Desktop/ciftiTools/vignettes"

To only read in certain columns, use the idx argument:

read_xifti(cifti_fnames["dtseries"], idx=2) # second column only
## Brain Structures: left cortex, right cortex  
##  left cortex: 30424 surface vertices, 1 measurements.
##  right cortex: 30527 surface vertices, 1 measurements.
## Intent: 3002 (dtseries).

Writing

When "xifti" object is written to files, the CIFTI components are placed in a CIFTI file and the surface geometries, if any, are placed in GIFTI files.

out_dir <- "output"

write_xifti(
  xii, file.path(out_dir, "my_cifti.dtseries.nii"), 
  file.path(out_dir, "my_L.surf.gii"), file.path(out_dir, "my_R.surf.gii")
)
## Writing left cortex.
## Writing right cortex.
## Creating CIFTI file from separated components.
## Writing surface geometry GIFTI(s).

With separate_cifti, a CIFTI can be separated and written into its component parts: the cortical data can be written to GIFTI metric files, and the subcortical data can be written to a NIFTI file. In addition, any ROIs or labels will also be written to files. The files are automatically named unless a new file name is provided.

# Use default names for everything except left cortex
separated_fnames = separate_cifti(
  cifti_fnames["dscalar_ones"], brainstructures="all", 
  cortexL_fname="my_left_cortex.func.gii", write_dir = out_dir
)
# Files written to `out_dir`, or current working dir. if not specified
basename(separated_fnames)
## [1] "my_left_cortex.func.gii" "ones_1k.ROI_L.func.gii" 
## [3] "ones_1k.R.func.gii"      "ones_1k.ROI_R.func.gii" 
## [5] "ones_1k.nii"             "ones_1k.labels.nii"     
## [7] "ones_1k.ROI.nii"

Separated files can be read into R with the oro.nifti/RNifti and gifti packages, and combined into a "xifti" object with as.xifti.

Visualization

ciftiTools graphics are made possible by the rgl package. To prepare the R Markdown document for knitting we need to do the following:

library(rgl)
rgl::setupKnitr()

# Sometimes the first RGL window does not render properly.
rgl::rgl.open(); rgl::rgl.close()

Now let’s take a look!

Surface visualization

view_xifti_surface(xii) displays the cortical data on the surface mesh. This function has several primary arguments:

Let’s see an example using each color_mode option. Note how the included surfaces are used in the first plot, but if none are present as in the second and third plots, the default surfaces are automatically used for visualization. We’ll also make the second plot interactive by requesting display of two idx. Try clicking and dragging around the second plot to rotate, and scrolling to zoom in and out.

# Normally `cex.title` doesn't need to be set, as it defaults to a good choice.
#   But when knitting static images this way, the default becomes a bit too big
#   based on how knitting works.
view_xifti_surface(xii, idx=1, zlim=c(1,2), title='color_mode = "sequential"', cex.title=1.3)

dtseries file; first column; sequential palette

xii <- read_cifti(cifti_fnames["dscalar"]) # no GIFTI included, so the default inflated surface is used.
view_xifti_surface(
  xii, idx=1:2, zlim=c(0,5), color_mode = "diverging",
  title='color_mode = "diverging"', cex.title=1.3
)
dlabel <- view_xifti_surface(
  read_cifti(cifti_fnames["dlabel"]), 
  # Interactively, a color legend that displays the label names will also be printed.
  legend_ncol=5, 
  title='color_mode = "qualitative"', cex.title=1.3
)

dlabel file; first label; palette from label metadata

(Note that the dtseries used in this example does not truly contain fMRI BOLD timeseries data, but we use it for illustration.)

Volume visualization

view_xifti_volume(xii) displays the subcortical data in slices. To view interactively in a web browser, set interactive=TRUE. By default, a series of slices is displayed overlaid on the MNI template. The orientation, numbers of slices, index and value range can be adjusted.

# cifti_fnames["dscalar_ones"] is the only file with subcortical data
xii <- read_cifti(cifti_fnames["dscalar_ones"], brainstructures="subcortical")
view_xifti_volume(xii)
## Values to be plotted range from 1 to 1.

Subcortical data (all ones)

# For information only, since papaya viewer cannot be opened during knitting
view_xifti_volume(xii, interactive = TRUE)

The S3 method plot(xii) will display the cortical data if possible, and the subcortical data otherwise.

Resampling and smoothing

Resampling

ciftiTools can resample CIFTI files to a lower resolution. Here, we resample the 32k dtseries file to 6k vertices. We also provide the surfaces and resample them in conjunction.

resampled_xii_fname <- "my_new_resampled.dtseries.nii"
resampled_surfL_fname <- "my_resampled_surfL.surf.gii"
resampled_surfR_fname <- "my_resampled_surfR.surf.gii"
  
xii_6k <- resample_cifti(
  cifti_fnames["dtseries"], resampled_xii_fname,
  resamp_res = 2000,
  surfL_fname, surfR_fname,
  resampled_surfL_fname, resampled_surfR_fname,
  write_dir = out_dir
)
## Separating CIFTI file.
## Time difference of 4.927585 secs
## Resampling CIFTI file.
## Time difference of 7.345121 secs
## Merging components into a CIFTI file... 
## Time difference of 0.1514442 secs
basename(xii_6k)
## [1] "my_new_resampled.dtseries.nii" "my_resampled_surfL.surf.gii"  
## [3] "my_resampled_surfR.surf.gii"

Resampling can also be performed while reading a file into R.

read_cifti(cifti_fnames["dscalar"], resamp_res=2000)
## Brain Structures: left cortex, right cortex  
##  left cortex: 1856 surface vertices, 2 measurements.
##  right cortex: 1863 surface vertices, 2 measurements.
## Intent: 3006 (dscalar).

Smoothing

Use smooth_cifti to perform smoothing. This function works for both CIFTI files and "xifti" objects.

smoothed_xii_fname <- "my_smoothed_cifti.dtseries.nii"
smooth_cifti(
  cifti_fnames["dtseries"], file.path(out_dir, smoothed_xii_fname),
  surf_FWHM=2, vol_FWHM=2,
  surfL_fname=surfL_fname, surfR_fname=surfR_fname,
  subcortical_zeroes_as_NA=TRUE
)
# Demonstrating the ability to use RColorBrewer palettes!
plot(
  read_cifti(file.path(out_dir, smoothed_xii_fname)), 
  surfL=surfL_fname, surfR=surfR_fname, 
  zlim=c(1,2), color_mode="diverging", colors="Spectral"
)

Smoothed CIFTI

Manipulation & Math

You can treat the "xifti" as a data matrix with the base R functions as.xifti, nrow and ncol.

dim(as.matrix(xii2))
## [1] 60951     2

apply_xifti applies a function along the rows or columns of a "xifti". The base R apply function also works on "xifti" objects. The difference is that when applying a function along the rows, the former will return a "xifti" whereas the latter will return a data matrix.

xiiL <- read_xifti(cifti_fnames["dscalar"], brainstructures="left")
gmeans <- apply_xifti(xiiL, 1, mean) # Mean of each greyordinate
gmeans
## Brain Structures: left cortex  
##  left cortex: 5412 surface vertices, 1 measurements.
## Intent: 3006 (dscalar).
cquants <- apply_xifti(xiiL, 2, quantile, c(.1, .5)) # Quantiles of each column
cquants
##     MyelinMap_BC_decurv corrThickness
## 10%            1.178208      2.184971
## 50%            1.305912      2.766169

combine_xifti combines multiple "xifti"s with different brainstructures.

xiiR <- read_xifti(cifti_fnames["dscalar"], brainstructures="right")
xii <- combine_xifti(xiiL, xiiR)
xii
## Brain Structures: left cortex, right cortex  
##  left cortex: 5412 surface vertices, 2 measurements.
##  right cortex: 5434 surface vertices, 2 measurements.
## Intent: 3006 (dscalar).

convert_xifti converts the intent of a "xifti".

xii <- convert_xifti(xii, "dtseries")
xii$meta$cifti$intent
## [1] 3002

merge_xifti concatenates metric data from multiple "xifti"s column-wise.

xii <- merge_xifti(xii, xii) # Columns are repeated twice now.

remove_xifti removes a brainstructure(s) from a "xifti".

xii <- remove_xifti(xii, "cortex_left") # Now only the right cortex data is included.
xii
## Brain Structures: right cortex  
##  right cortex: 5434 surface vertices, 4 measurements.
## Intent: 3002 (dtseries).

select_xifti subsets or re-orders the columns of the metric data of a "xifti".

xii <- select_xifti(xii, c(4,3,2)) # Reverse column order & drop the first.
xii
## Brain Structures: right cortex  
##  right cortex: 5434 surface vertices, 3 measurements.
## Intent: 3002 (dtseries).

S3 methods allow for univariate transformations of "xifti" objects as well as arithmetic operations of multiple "xifti" objects.

print(max(as.matrix(xii)))
## [1] 4.63584
xii <- 1 + exp(xii) + (xii / 3)
print(max(as.matrix(xii)))
## [1] 105.6598

Working with surfaces

ciftiTools also includes functionality for working with surface geometry GIFTI files separately from any metric data. Surfaces that are read in are "surf" objects:

# Reading
ciftiTools.setOption("surf", "midthickness") # let's use a different surface here
surf <- read_surf(demo_files()$surf["left"])
surf
## Vertices:  32492 
## Faces:  64980 
## Hemisphere:  left

These can be written back to GIFTI files, visualized, and resampled. Resampling can be performed on the "surf" objects or the surface GIFTI files directly:

# Writing
write_surf_gifti(surf, file.path(out_dir, "my.L.surf"))

# Visualizing
plot(surf)

# Resample a `"surf"` object
surf <- resample_surf(surf, 2000, "left")
# Resample a GIFTI file
resample_gifti(surfL_fname, file.path(out_dir, "my.L.2k.surf.gii"), "left", resamp_res=2000)

Lastly, let’s demonstrate the ability to plot vertices and edges. (This can also be done when plotting "xifti" objects using the same arguments.)

# Recall that surf was resampled to 2k
plot(surf, vertex_size=3, vertex_color="blue")

plot(surf, edge_color="black")